﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using INF1010_Chat;
using System.Threading;
using System.Net;

namespace ServerChat
{
    public class ServerChat
    {
        public delegate void ReceivedRefreshClientsEventHandler(object sender, EventArgs args);
        public event ReceivedRefreshClientsEventHandler RefreshList;

        private static ServerChat serverSingleton;

        private State etat;
        private IPAddress serverIP;
        private int serverPort;

        private List<Thread> responseThreads;
        private Thread threadRendezVous;

        private UserList users;
        private GroupList groups;

        private ServerChat()
        {
            etat = State.Arreter;
            users = new UserList();
            groups = new GroupList();
            responseThreads = new List<Thread>();
        }

        /*
         * Retourne la liste des utilisateurs connectés 
         */
        public List<User> Users
        {
            get
            {
                return users.Users;
            }
        }

        public virtual void OnRefreshList(EventArgs args)
        {
            if (RefreshList != null)
                RefreshList(this, args);
        }

        /*
         * Démarre le serveur 
         */
        public void Start(string serverIP, int port)
        {
            etat = State.Demarrer;
            this.serverIP = IPAddress.Parse(serverIP);
            this.serverPort = port;

            //On instancie le thread qui écoute les connexions et on démarre son exécution
            threadRendezVous = new Thread(Listen);

            threadRendezVous.Start();
        }

        /*
         * Arrête le serveur 
         */
        public void Stop()
        {
            etat = State.Pause;
            //Attente de la fermeture des threads
            threadRendezVous.Join();
            DateTime time = DateTime.Now;
            while (responseThreads.Where(rt => rt.ThreadState == ThreadState.Running).Any() && DateTime.Now < time.AddSeconds(5))
            {
                Thread.Sleep(1);
            }

            responseThreads.Clear();

            //Déconnexion des utilisateurs
            foreach (User user in users.Users)
            {
                SendDisconnect(user);
            }

            users = new UserList();
            groups = new GroupList();
            etat = State.Arreter;
        }

        /*
         * Retourne l'instance du serveur
         */
        public static ServerChat GetServer()
        {
            if (serverSingleton == null)
                serverSingleton = new ServerChat();

            return serverSingleton;
        }

        /*
         * Méthode exécutée par le thread de redez-vous. Écoute les connexions de clients 
         */
        public void Listen()
        {
             TcpListener listener = null;
            try
            {
                listener = new TcpListener(new IPEndPoint(serverIP, serverPort));
                while (etat == State.Demarrer)
                {
                    listener.Start(200);
                    if (!listener.Pending())
                    {
                        Thread.Sleep(1);
                        continue;
                    }

                    Socket socketCommunication = listener.AcceptSocket();

                    User newUser = new User(socketCommunication);
                    newUser.SomethingReceived += new User.SomethingReceivedEventHandler(user_SomethingReceived);
                    users.AddUser(newUser);
                    //démarrage de la communication entre le client et le serveur
                    newUser.StartThreadReceive();

                }
                listener.Stop();
            }
            catch (ThreadAbortException abortException)
            {
                Console.WriteLine("Ignore abort exception : " + abortException.Message);
            }
            finally
            {
                if (listener != null)
                    listener.Stop();
            }
        }

        /*
         * Évènement lancé lorsque l'utilisateur reçois quelque chose 
         */
        void user_SomethingReceived(object sender, EventArgs args)
        {
            User currentUser = (User)sender;
            ParseResponse(currentUser, currentUser.DequeueMessage());
        }

        /*
         * Lis la réponse reçu par le serveur pour déterminer de quelle commande il s'agit et que faire avec 
         */
        public void ParseResponse(User currentUser, string message)
        {
            Thread handlingThread = null;
            Group group;
            User userDestination;
            string command = String.Empty;
            if (message.Length > 0)
            {
                command = message.Substring(0, 2);
                message = message.Substring(2);
            }

            switch (command)
            {
                case Commands.SendListUser:
                    handlingThread = new Thread(() => SendRefreshList(currentUser));
                    break;
                case Commands.SendToGroup:
                    group = groups.Groups.FirstOrDefault(g => g.Name.Equals(message.Substring(1, message.IndexOf("}") - 1)));
                    message = message.Substring(message.IndexOf("}") + 1);
                    handlingThread = new Thread(() => SendToGroup(currentUser, group, message));
                    break;
                case Commands.SendToAll:
                    SendToAll(currentUser, message);
                    break;
                case Commands.SendToUser:
                    userDestination = users.Users.FirstOrDefault(g => g.Name.Equals(message.Substring(1, message.IndexOf("}") - 1)));
                    message = message.Substring(message.IndexOf("}") + 1);
                    handlingThread = new Thread(() => SendToUser(currentUser, userDestination, message));
                    break;
                case Commands.JoinGroup:
                    group = groups.Groups.FirstOrDefault(g => g.Name.Equals(message));
                    if (group == null)
                        group = groups.AddGroup(message);
                    handlingThread = new Thread(() => UserJoinGroup(currentUser, group));
                    break;
                case Commands.LeaveGroup:
                    group = groups.Groups.FirstOrDefault(g => g.Name.Equals(message));
                    handlingThread = new Thread(() => UserLeaveGroup(currentUser, group));
                    break;
                case Commands.Authentify:
                    handlingThread = new Thread(() => UserAuthenticate(currentUser, message));
                    break;
                case Commands.Unauthentify:
                    handlingThread = new Thread(() => UserDisconnect(currentUser));
                    break;
                default:
                    handlingThread = new Thread(() => UserDisconnect(currentUser));
                    break;
            }
            if (handlingThread != null)
            {
                responseThreads.Add(handlingThread);
                handlingThread.Start();
            }
        }

        /*
         * Authentifie un utilisateur avec son nom d'utilisateur 
         */
        public void UserAuthenticate(User user, string userName)
        {
            user.Authenticate(userName);
            foreach (User u in users.Users)
            {
                SendRefreshList(u);
            }
            OnRefreshList(new EventArgs());
        }

        /*
         * Déconnecte un utilisateur 
         */
        public void UserDisconnect(User user)
        {
            foreach (Group group in groups.Groups)
            {
                group.RemoveUser(user);
            }

            user.CommunicationSocket.Shutdown(SocketShutdown.Send);
            user.CommunicationSocket.Disconnect(false);
            users.RemoveUser(user);
            foreach (User u in users.Users)
            {
                SendRefreshList(u);
            }
            OnRefreshList(new EventArgs());
        }

        /*
         * Envoi la liste des utilisateurs connectés 
         */
        public void SendRefreshList(User user)
        {
            byte[] buffer = Encoding.Default.GetBytes(Commands.SendListUser + users.Serialize());
            if (user.CommunicationSocket != null)
            {
                user.CommunicationSocket.Send(buffer);
            }
        }

        /*
         * Ajoute un utilisateur à un groupe 
         */
        public void UserJoinGroup(User user, Group group)
        {
            group.AddUser(user);
        }

        /*
         * Retire un utilisateur d'un groupe 
         */
        public void UserLeaveGroup(User user, Group group)
        {
            group.RemoveUser(user);
            if (!group.Users.Any())
                groups.RemoveGroup(group.Name);
        }

        /*
         * Envoi un message en broadcast 
         */
        public void SendToAll(User userSource, string message)
        {
            string fullMessage = String.Format("{0}{1} --> Tous: {2}", Commands.SendToAll, userSource, message);
            byte[] buffer = Encoding.Default.GetBytes(fullMessage);
            foreach (User user in users.Users.Where(u => !u.Equals(userSource)))
            {
                if (user.CommunicationSocket != null)
                {
                    user.CommunicationSocket.Send(buffer);
                }
            }
        }

        /*
         * Envoi un message en multicast 
         */
        public void SendToGroup(User userSource, Group groupDestination, string message)
        {
            string fullMessage = String.Format("{0}{1} --> Group {2}: {3}", Commands.SendToGroup, userSource, groupDestination.Name, message);
            byte[] buffer = Encoding.Default.GetBytes(fullMessage);
            foreach (User user in groupDestination.Users.Where(u => !u.Equals(userSource)))
            {
                if (user.CommunicationSocket != null)
                {
                    user.CommunicationSocket.Send(buffer);
                }
            }
        }

        /*
         * Envoi un message en unicast 
         */
        public void SendToUser(User userSource, User userDestination, string message)
        {
            string fullMessage = String.Format("{0}{1} --> Vous: {2}", Commands.SendToUser, userSource, message);
            byte[] buffer = Encoding.Default.GetBytes(fullMessage);
            if (userDestination.CommunicationSocket != null)
            {
                userDestination.CommunicationSocket.Send(buffer);
            }
        }

        /*
         * Envoi une déconnexion à un utilisateur 
         */
        public void SendDisconnect(User user)
        {
            byte[] buffer = Encoding.Default.GetBytes(Commands.Unauthentify);
            if (user.CommunicationSocket != null)
            {
                user.CommunicationSocket.Send(buffer);
            }

            user.CommunicationSocket.Shutdown(SocketShutdown.Send);
            user.CommunicationSocket.Disconnect(false);

            foreach (Group group in groups.Groups)
            {
                group.RemoveUser(user);
            }
            users.RemoveUser(user);
            OnRefreshList(new EventArgs());
        }
    }
}
